﻿// v:0.1

package  II.util {
	
	import flash.text.*;
	import flash.display.*;
	import flash.utils.*;
	
	
	public class Eval {
	
		// STATIC
		
		private static var aAlias:Dictionary = new Dictionary();
			
		// Permette di inserire un alias... gli alias entrano in gioco nella "preInterpreter"
		public static function addAlias ( name:String, alias:String ) : void {
			if ( name.length > 0 && alias.length > 0 ) {
				Eval.aAlias[name] = alias;
			} else if ( name.length > 0 ) {
				delete Eval.aAlias[name];
			}
		}
		
		// Restituisce un oggetto che indica l'apertura e la chiusura di due caratteri (tipicamente parentesi o virgolette)
		// da notare che conta le aperture e le chiusure
		public static function GetSubstrIntoOpenAndClose ( str:String, carOpen:String, carClose:String ):Object {
			var ret:Object = new Object();
			ret.start = str.indexOf ( carOpen );
			if ( ret.start != -1 ) {
				ret.start++;
				var contOpen:Number = 1;
				var car:String;
				for ( var cont:Number = ret.start; cont < str.length; cont++ ) {
					car = str.charAt(cont);
					if ( car == carOpen ) {
						contOpen++;
					} else if ( car == carClose ) {
						contOpen--;
						if ( contOpen == 0) {
							break;
						}
					}
				}
				if ( contOpen > 0 ) {
					trace ( "errore: non ho trovato la chiusura " + carClose );
					ret.start = -1;
				} else {
					ret.end = cont
				}
			}
			return ret;
		}
		
		// Semplice trim della stringa (as3 non lo supporta direttamente)
		public static function trim ( str:String ):String {
			for ( var cont:Number = 0
				 ; cont < str.length && (str.charCodeAt(cont) == 32) 
				 ; cont++ ) {	}
			if ( cont == str.length ) return "";
			for ( var cont2:Number = str.length-1
				 ; cont2 > cont && (str.charCodeAt(cont2) == 32) 
				 ; cont2-- ) {}
			cont2++;
			return str.substring ( cont, cont2 );
		}

		// END STATIC


		// PROPRIETIES
		
		private var root:Object = null;
		private var objResolve:Object = null;
		
		// END PROPERTIES
	
	
	
		// CONSTRUCTORS
		
		public function Eval( root:Object, resolver:Object ){
			this.root = root!=null? root : this;
			this.objResolve = resolver;
		}
		
		// END CONSTRUCTORS

		
	
	
	
	// Permette di valutare una stringa e ricavare il risultato
	public function evaluate ( exp:String ):EvalReturn {
		var ret:String = this.preInterpreter ( exp );
		var evalRet:EvalReturn = this.interpreter ( new EvalReturn(ret) );
		return evalRet;
	}

	// in pratica sostituisce tutte 'occorrenze' specificate come "alias"
	private function preInterpreter ( str:String ):String {
		if ( str.length > 0 ) {
			
			var tmp:String; 
			var ret:String = str;
			var index:Number = 0;
			var cont:Number;
			var alias:String;
			var substitute:String;
			
			var indexAlias:Number = -1;
			var cur:String;
			for ( cur in Eval.aAlias ) { //cont = 0; cont < Eval.aAlias.length; cont++ ) {
				alias = cur;
				substitute = Eval.aAlias[cur];
				index = 0;
				tmp = new String ();
				do {
					indexAlias = ret.indexOf(alias, index);
					if ( indexAlias != -1 ) {
						tmp += ret.substring ( index, indexAlias );
						tmp += substitute;
						index = indexAlias + alias.length;
					} 
				} while ( indexAlias != -1 )
				ret = tmp + ret.substr( index );
			}
			return ret;
		}
		return str;
	}

	// interpreta un EvalReturn, questa funzione è richiamata ricorsivamente per risolvere le priorità
	private function interpreter ( exp:EvalReturn ):EvalReturn {
		
		var evalRet:EvalReturn = new EvalReturn();
		var car:String = "";		// stringa fino al questo momento costruita dall'assemblaggio della variabile car.
		var code:Number;		// il carattere che si sta analizzando

		for ( var cont:Number = 0; cont < exp.valueStr.length; cont++ ) {
			car += exp.valueStr.charAt(cont);
			code = exp.valueStr.charCodeAt(cont);

			// e' un carattere che si puo' trascurare?
			if ( this.isNoValidChar(code) ) {
				car = "";
				continue;
				
			// è un alfanumerico?  1 2 3 4 5 6 7 8 9 0 a-z A-Z  .  _ $ { }
			} else if ( this.isValidChar(code) ) {
				if ( code == 123 ) {
					var delim:Object = Eval.GetSubstrIntoOpenAndClose ( exp.valueStr, "{", "}" );
					if ( delim.start != -1 ) {
						var sub:String = exp.valueStr.substring( delim.start-1, delim.end+1 );
						car = sub;
						cont += (delim.end-delim.start)+1;
					}
				}
				evalRet.value = evalRet.valueStr + car;				
				car = "";
			
			// e' uno spazio
			} else if ( code == 32 ) {
				if ( evalRet.valueStr == "new" ) {
					var delim:Object = Eval.GetSubstrIntoOpenAndClose ( exp.valueStr, "(", ")" );
					if ( delim.start != -1 ) {
						var sub:String = exp.valueStr.substring( delim.start, delim.end );
						var par:EvalReturn;
						if ( sub.length > 0 ) {
							par = this.interpreter ( new EvalReturn ( sub ) );
						}
						var nameObject:String = Eval.trim ( exp.valueStr.substring( cont, delim.start-1 ) )
						evalRet.value = nameObject;
						if ( evalRet.type == EvalReturn.TYP_VARIABLE ) {
							evalRet = evalRet.createNew ( par, this );
						} else {
							return null;
						}
						cont = delim.end;
						car = "";
					} else {
						return null;
					}
				} else {
					car="";
					continue;
				}
				
			// è una parentesi aperta?  (
			} else if ( code == 40 ) {
				var delim:Object = Eval.GetSubstrIntoOpenAndClose ( exp.valueStr, "(", ")" );
				if ( delim.start != -1 ) {
					var sub:String = exp.valueStr.substring( delim.start, delim.end );
					var par:EvalReturn;
					if ( sub.length > 0 ) {
						par = this.interpreter ( new EvalReturn ( sub ) );
					}
					if ( evalRet.type == EvalReturn.TYP_VARIABLE ) {
						evalRet = evalRet.execute( par, this );
					} else {
						evalRet = par;
					}
					cont = delim.end;
					car = "";
				} else {
					return null;
				}

			// è una parentesi quadra?  [
			} else if ( code == 91 ) {
				var delim:Object = Eval.GetSubstrIntoOpenAndClose ( exp.valueStr, "[", "]" );
				if ( delim.start != -1 ) {
					var sub:String = exp.valueStr.substring( delim.start, delim.end );
					var par:EvalReturn;
					if ( sub.length > 0 ) {
						par = this.interpreter ( new EvalReturn ( sub ) );
					}
					if ( evalRet.type == EvalReturn.TYP_VARIABLE ) {
						evalRet = evalRet.getArray( par, this );
					} else {
						return null;
					}
					cont = delim.end;
					car = "";
				} else {
					return null;
				}
				
			// è un doppio apice? "
			} else if ( code == 34 ) {
				var indexApice:Number = exp.valueStr.indexOf("\"", cont+1);
				if ( indexApice != -1 ) {
					evalRet = new EvalReturn(exp.valueStr.substring(cont+1, indexApice));
					evalRet.type = EvalReturn.TYP_STRING;
					cont = indexApice;
					car = "";
				} else {
					trace( "ERROR: syntax (non c'e' la chiusura dell'apice)");
					return null;
				}
			/*
			} else if ( code == 39 ) {
				var indexApice:Number = exp.valueStr.indexOf("'", cont+1);
				if ( indexApice != -1 ) {
					evalRet = new EvalReturn(exp.valueStr.substring(cont+1, indexApice));
					evalRet.setType( EvalReturn.TYP_STRING );
					cont = indexApice;
					car = "";
				} else {
					trace( "ERROR: syntax (non c'e' la chiusura dell'apice)");
					return null;
				}
				*/
			// è qualcos'altro?  (operatore, per esempio: * / + -)
			} else {
				
				// se l'operatiore non e' il primo carattere va bene 
				if  ( 	evalRet.type == EvalReturn.TYP_STRING  
						|| evalRet.valueStr.length > 0 
						|| evalRet.objArray != null 
				) {
					
					// ricavo il valore di quello che sta dopo l'operatore, se restituisce null allora vuol dire che c'e' qualcosa che non va provo a prendere in considerazione anche il prossimo carattere.
					var evalNext:EvalReturn = this.interpreter (  new EvalReturn(exp.valueStr.substring(cont+1)) );

					if ( car == "," ) {
						if ( evalNext.type == EvalReturn.TYP_PARAMETERS ) {
							evalNext.value.unshift( evalRet );
							evalRet = evalNext;
						} else {
							var aValue:Array = new Array();
							aValue.push ( evalRet );
							aValue.push ( evalNext );
							evalRet = new EvalReturn();
							evalRet.value = aValue;
							evalRet.type = EvalReturn.TYP_PARAMETERS;
						}
						break;
					} else if ( evalNext == null ) {
						continue;
					}
					
					if ( car == "=" ) {
						evalRet.assign ( evalNext, this );
					} else {
						if ( evalRet.operator ( car, evalNext, this ) == false ){
							continue;
						}
					}
					break;
					
				// se e' il primo carattere c'e' qualcosa che non va perke' un operatore non piu' essere il primo carattere. ritorna null
				} else {
					trace ( "ERROR: operatore senza parametri" );
					return null;
				}
			}
		}
		
		evalRet.resolveVariable(this);
		return evalRet;
	}
	
	//  1 2 3 4 5 6 7 8 9 0 a-z A-Z  .  _ $
	private function isValidChar ( code:Number ):Boolean {
		return (code >= 48 && code <= 57) 
					|| (code >= 65 && code <= 90)
					|| (code >= 97 && code <= 122)
					|| code == 95
					|| code == 46
					|| code == 36
					|| code == 123
					|| code == 125;
	}
	
	// <space> <enter>
	private function isNoValidChar ( code:Number ):Boolean {
		return 	code == 13 || code == 10;
	}



	
	

	// Restituisce un oggetto con i parametri "refParent" e "refName"
	// "refParent" rappresenta l'oggetto che ha la proprietà in esame
	// "refName" è il nome della proprietà
	// questo perche' cosi' posso assegnare un valore alla proprietà
	public function getRef ( name:String ):Object {
		var ret:Object = new Object();
		var ref:Object = this.root;
			if ( name.indexOf ( "$node_last" ) == 0 ) {
				var delim:Object = Eval.GetSubstrIntoOpenAndClose ( name, "{", "}" );
				if ( delim.start != -1 ) {
					ref = this.objResolve.actionLast.getNodeWPath ( name.substring ( delim.start, delim.end ) );
					name = name.substr(delim.end + 2);
				}
			} else if ( name.indexOf ( "$node" ) == 0 ) {
				var delim:Object = Eval.GetSubstrIntoOpenAndClose ( name, "{", "}" );
				if ( delim.start != -1 ) {
					ref = this.objResolve.getActionSel().getNodeWPath ( name.substring ( delim.start, delim.end ) );
					name = name.substr(delim.end + 2);
				}
			} 
		var aVarNames:Array = name.split(".");
		var tmpVarName:String;
		
		// ricavo l'oggetto che contiene la variabile
		for (var cont = 0; cont < aVarNames.length-1; cont++ ) {
			tmpVarName = aVarNames[cont];
			if ( tmpVarName.charAt(0) == "$" ) {
				ref = this.objResolve.getVariableSys(tmpVarName.substr(1));
			} else if ( tmpVarName == "_parent" ) {
				ref = ref._parent;
			} else {
				ref = ref[tmpVarName];
			}
		}
		ret.refName = aVarNames[cont]
		if ( ret.refName.charAt(0) == "$" ) {
			ret.refParent = this.objResolve.getVariableSys(ret.refName.substr(1));
			ret.refName = null;
		} else {
			ret.refParent = ref;
		}
		return ret;
	}
	function setVariable ( name:String, value:Object ):Object {
		if ( name.length > 0 ) {
			var ref:Object = this.getRef( name );
			var objRef = ref.refParent[ref.refName];
			
			//if ( objRef != null ) {
				/*
				if ( ref.refParent instanceof II.gui.LightInterface ) {
					if( ref.refParent.getGst() instanceof II.gui.link.ButtonLink ) {
						ref.refParent.getGst()[ref.refName] = Number(value);
						objRef = ref.refParent.getGst();
						return objRef;		
					}
				}
				*/
				// se e' una TextField setto il suo valore
			    
				if ( objRef instanceof TextField ) {
					if ( value.charAt(0) == "#" ) {
						objRef.textColor = parseInt( value.substr( 1 ), 16 );
					} else {
						if ( objRef.html == true ) {
							objRef.htmlText = value;
						} else {
							objRef.text = value;
						}
					}
					
				// se e' un oggetto Light allora setta il valore come dato (NON CONTROLLATO)
				//} else if ( objRef instanceof II.gui.LightInterface ) {
				//	objRef.getGst().setData( value );
				//	objRef.getGst().update();
					
				// se e' un instanza di MovieClip carica dinamicamente con path di url uguale al volore
				} else if ( objRef instanceof MovieClip ) {
					if ( value.charAt(0) == "#" ) {
						//var color = new Color ( objRef );
						//color.setRGB ( parseInt( value.substr( 1 ), 16 ) );
					} else {
						objRef.loadMovie ( value );
					}
					
				// e' qualcos'altro
				} else {
					switch ( typeof(objRef) ) {
						case "boolean":
							ref.refParent[ref.refName] = (value.toString().toLowerCase() == "true");
						break;
						case "number":
							ref.refParent[ref.refName] = Number(value);
						break;
						case "function":
							ref.refParent[ref.refName](value);
						break;
						default:
							ref.refParent[ref.refName] = value;
						break;
					}
				}
			//}
			return objRef;
		} else {
			return null;
		}
	}
	public function getVariable ( name:String ):Object {
		if ( name.length > 0 ) {
			var ref:Object = this.getRef( name );
			if ( ref.refName != null ) {
				return ref.refParent[ref.refName];
			} else {
				return ref.refParent;
			}
		} else {
			return null;
		}
	}
	
	
	
	
}

}